package com.example;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

/**
 * Redis分段锁优化并发扣减，降低锁粒度，进而减少锁竞争
 */
@Slf4j
@Component
public class RedisSegmentLock {

	private static final int SEGMENT_COUNT = 20;
	private static final String LOCK_PREFIX = "lock:";
	private static final String STOCK_PREFIX = "stock:";
	private static final String SEGMENT_SUFFIX = ":segment:";
	private static final long LOCK_EXPIRE_TIME = 3000; // 3秒

	private final RedisTemplate<String, Object> redisTemplate;

	public RedisSegmentLock(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	/**
	 * 获取分段库存键
	 */
	private String getSegmentKey(String sku, String userId) {
		int segment = Math.abs((sku + "_" + userId).hashCode()) % SEGMENT_COUNT;
		return STOCK_PREFIX + sku + SEGMENT_SUFFIX + segment;
	}

	/**
	 * 获取分段锁键
	 */
	private String getLockKey(String segmentKey) {
		return LOCK_PREFIX + segmentKey;
	}

	/**
	 * 尝试获取分段锁
	 */
	private boolean tryLock(String lockKey, String requestId) {
		return Boolean.TRUE.equals(redisTemplate.opsForValue()
		                                        .setIfAbsent(lockKey, requestId, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS));
	}

	/**
	 * 释放锁
	 */
	private boolean unLock(String lockKey, String requestId) {
		String script = """
				        if redis.call('get', KEYS[1]) == ARGV[1] then
				            return redis.call('del', KEYS[1])
				        else
				            return 0
				        end
				        """;

		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
		Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
		return result != null && result == 1;
	}

	/**
	 * 扣减库存
	 */
	public boolean deductStock(String sku, String userId, int quantity) {
		String segmentKey = getSegmentKey(sku, userId);
		String lockKey = getLockKey(segmentKey);
		String requestId = UUID.randomUUID().toString();

		try {
			// 1. 尝试获取锁
			if (!tryLock(lockKey, requestId)) {
				log.warn("获取锁失败, sku:{}, userId:{}, segmentKey:{}", sku, userId, segmentKey);
				return false;
			}

			// 2. 检查并扣减库存
			Long currentStock = redisTemplate.opsForValue().increment(segmentKey, 0);
			if (currentStock == null || currentStock < quantity) {
				log.warn("库存不足, sku:{}, userId:{}, segmentKey:{}, current:{}, require:{}",
				         sku, userId, segmentKey, currentStock, quantity);
				return false;
			}

			// 3. 执行扣减
			redisTemplate.opsForValue().decrement(segmentKey, quantity);
			log.info("扣减成功, sku:{}, userId:{}, segmentKey:{}, quantity:{}", sku, userId, segmentKey, quantity);
			return true;
		} finally {
			// 4. 释放锁
			unLock(lockKey, requestId);
		}
	}

	/**
	 * 初始化库存（均匀分配到各分段）
	 */
	public void initStock(String sku, int totalStock) {
		int baseStock = totalStock / SEGMENT_COUNT; // 每个分段的基础库存量
		int remainder = totalStock % SEGMENT_COUNT; // 分配后剩余的库存量

		for (int i = 0; i < SEGMENT_COUNT; i++) {
			String segmentKey = STOCK_PREFIX + sku + SEGMENT_SUFFIX + i;
			// 均匀分配库存：将总库存尽可能均匀地分配到多个分段中。
			int segmentStock = baseStock + (i < remainder ? 1 : 0);
			redisTemplate.opsForValue().set(segmentKey, segmentStock);
			log.info("初始化库存分段, segmentKey:{}, stock:{}", segmentKey, segmentStock);
		}
	}

	/**
	 * 获取总库存
	 */
	public long getTotalStock(String sku) {
		long total = 0;
		for (int i = 0; i < SEGMENT_COUNT; i++) {
			String segmentKey = STOCK_PREFIX + sku + SEGMENT_SUFFIX + i;
			Integer stock = (Integer) redisTemplate.opsForValue().get(segmentKey);
			if (stock != null) {
				total += stock;
			}
		}
		return total;
	}

}
